# frozen_string_literal: true

require 'spec_helper'

RSpec.describe Gitlab::Llm::Templates::ExplainVulnerability, feature_category: :vulnerability_management do
  let_it_be(:source_code) do
    <<~SOURCE
    #include <stdio.h>

    int main(int argc, char *argv[])
    {
      char buf[8];
      memcpy(&buf, "123456789");
      printf("hello, world!");
    }
    SOURCE
  end

  let_it_be(:project) do
    create(:project, :custom_repo, files: {
      'src/main.c' => source_code
    })
  end

  let_it_be(:vulnerability_finding) do
    create(:vulnerabilities_finding,
      :sast,
      project: project,
      location: {
        'file' => 'src/main.c',
        'start_line' => 5,
        'end_line' => 6
      }
    )
  end

  let_it_be(:vulnerability) do
    create(:vulnerability, :sast, findings: [vulnerability_finding], project: project)
  end

  before do
    vulnerability.finding.project = project
    vulnerability.finding.clear_memoization(:source_code)
    vulnerability.finding.clear_memoization(:vulnerable_code)
  end

  subject { described_class.new(vulnerability) }

  describe '#presubmission_checks' do
    subject { described_class.new(vulnerability).presubmission_checks }

    let(:expected) { { potential_secrets_in_code: false, secret_detection_result: false } }
    let(:vulnerability) { create(:vulnerability, :sast, findings: [vulnerability_finding], project: project) }

    it { is_expected.to eq(expected) }

    context 'when the vulnerability is for a secret detection' do
      before do
        vulnerability.report_type = :secret_detection
        vulnerability.finding.location['file'] = 'src/main.c'
        vulnerability.finding.location['start_line'] = 5
      end

      it { is_expected.to eq(expected.merge(secret_detection_result: true)) }
    end

    context 'when the code possibly contains secret patterns' do
      [
        "encryption = 'AES-256'",
        "ENCRYPTION = 'AES-256'",
        "params: {
              email: someone@somewhere.something
            }",
        "authorisation = 'me@some_service'",
        "authorization = 'me@some_other_service'",
        "aUtHoRiZaTiOn = 'me@some_other_service'",
        "session_identifier = 'somevalue'"
      ].each do |pattern|
        context "with pattern #{pattern}" do
          before do
            finding = vulnerability.finding
            allow(finding).to receive(:vulnerable_code).and_return(pattern)
          end

          it { is_expected.to eq(expected.merge(potential_secrets_in_code: true)) }
        end
      end
    end
  end

  describe '#to_prompt' do
    subject { described_class.new(vulnerability).to_prompt(**prompt_options) }

    let(:prompt_options) { { include_source_code: nil } }
    let(:identifiers) { vulnerability.finding.identifiers.pluck(:name).join(", ") }
    let(:vulnerable_code) { source_code.lines[4..5].join }

    context 'when a file is provided' do
      context 'when the file exists' do
        before do
          vulnerability.finding.location['file'] = 'src/main.c'
          vulnerability.finding.location['start_line'] = 5
          vulnerability.finding.location['end_line'] = 6
        end

        let(:vulnerable_code) { source_code.lines[4..5].join }

        it { is_expected.to include(vulnerability.title) }
        it { is_expected.to include(identifiers) }
        it { is_expected.to include('"main.c"') }
        it { is_expected.to include(vulnerable_code) }
        it { is_expected.to eq(<<~PROMPT) }
          You are a software vulnerability developer.
          Explain the vulnerability "#{vulnerability.title} - (#{identifiers})".
          The file "#{File.basename(vulnerability.file)}" has this vulnerable code:

          ```
          #{vulnerable_code}
          ```

          Provide a code example with syntax highlighting on how an attacker can take advantage of the vulnerability.
          Provide a code example with syntax highlighting on how to fix it.
          Provide the response in markdown format with headers.
        PROMPT

        context 'when the code possibly contains secret patterns' do
          [
            "traceroute = \"127.0.0.1\"",
            "sentry_DSN = \"some_secret_value\"",
            "otp = \"ABCDEF\"",
            "JWT = \"89abddfb-2cff-4fda-83e6-13221f0c3d4f\"",
            "signing_certificate = 'heredoc'",
            "bearer_token = \"Bearer 89abddfb-2cff-4fda-83e6-13221f0c3d4f\"",
            "string uuid = \"123e4567-e89b-12d3-a456-426655440000\";",
            "c73bcdcc-2669-4bf6-81d3-e4ae73fb11fd",
            "C73BCDCC-2669-4Bf6-81d3-E4AE73FB11FD",
            "User.update(password: 'shouldntcommitthis'",
            "api_key = '12345'",
            "authorisation_token = \"9955996w3ah43\"",
            "encrypted_value = '12345'",
            "encryption = 'AES-256'",
            "ENCRYPTION = 'AES-256'",
            "params: {
              email: someone@somewhere.something
            }",
            "authorisation = 'me@some_service'",
            "authorization = 'me@some_other_service'",
            "aUtHoRiZaTiOn = 'me@some_other_service'",
            "session_identifier = 'somevalue'",
            <<~MULTILINESOURCE
              #include <stdio.h>

              int main(int argc, char *argv[])
              {
                char passkey[8];
                memcpy(&passkey, "123456789");
                printf("hello, world!");
              }
            MULTILINESOURCE
          ].each do |pattern|
            context "with pattern #{pattern}" do
              before do
                finding = vulnerability.finding
                allow(finding).to receive(:vulnerable_code).and_return(pattern)
              end

              it "does not send the prompt with the code" do
                expect(subject).not_to include(pattern)
              end
            end
          end
        end

        context 'when include_source_code is true' do
          let(:prompt_options) { { include_source_code: true } }

          it { is_expected.to include(vulnerable_code) }
        end

        context 'when include_source_code is false' do
          let(:prompt_options) { { include_source_code: false } }

          it { is_expected.not_to include(vulnerable_code) }
        end
      end

      context 'when the vulnerability is for a secret detection' do
        before do
          vulnerability.report_type = :secret_detection
          vulnerability.finding.location['file'] = 'src/main.c'
          vulnerability.finding.location['start_line'] = 5
        end

        it { is_expected.not_to include(vulnerable_code) }

        context 'when include_source_code is true' do
          let(:prompt_options) { { include_source_code: true } }

          it { is_expected.to be_nil }
        end
      end

      context 'when there is more vulnerable code than the maximum allowed' do
        let_it_be(:source_code) { "a" * (described_class::MAX_CODE_LENGTH + 1) }

        let_it_be(:project) do
          create(:project, :custom_repo, files: {
            'jquery.min.js' => source_code
          })
        end

        before do
          vulnerability.finding.location['file'] = 'jquery.min.js'
          vulnerability.finding.location['start_line'] = 1
        end

        it { is_expected.not_to include(source_code) }

        context 'when include_source_code is true' do
          let(:prompt_options) { { include_source_code: true } }

          it { is_expected.to be_nil }
        end
      end

      context 'when the file does not exist' do
        before do
          vulnerability.finding.location['file'] = 'src/missing.c'
        end

        let(:expected) do
          <<~PROMPT
          You are a software vulnerability developer.
          Explain the vulnerability "#{vulnerability.title} - (#{identifiers})".
          The vulnerable code is in the file "#{File.basename(vulnerability.file)}".
          Provide a code example with syntax highlighting on how an attacker can take advantage of the vulnerability.
          Provide a code example with syntax highlighting on how to fix it.
          Provide the response in markdown format with headers.
          PROMPT
        end

        it { is_expected.to eq(expected) }

        context 'when include_source_code is true' do
          let(:prompt_options) { { include_source_code: true } }

          it { is_expected.to be_nil }
        end
      end
    end

    context 'when a file is not provided' do
      before do
        vulnerability.finding.location.delete('file')
      end

      let(:expected) do
        <<~PROMPT
        You are a software vulnerability developer.
        Explain the vulnerability "#{vulnerability.title} - (#{identifiers})".
        Provide a code example with syntax highlighting on how an attacker can take advantage of the vulnerability.
        Provide a code example with syntax highlighting on how to fix it.
        Provide the response in markdown format with headers.
        PROMPT
      end

      it { is_expected.to eq(expected) }

      context 'when include_source_code is true' do
        let(:prompt_options) { { include_source_code: true } }

        it { is_expected.to be_nil }
      end
    end
  end
end
